<?php
/**
 * 基幹クラス定義ファイル
 * @author		risukan@hotmaill.com
 * @version		0.70
 * @link		http://bouyke.com/
 */
/**
 * ゆとりPHP オブジェクトローダー
 * <pre>
 * $cls = new Yutori();
 * </pre>
 *  
 * @author		risukan@hotmaill.com
 * @version		0.90
 * @link		http://bouyke.com/
 */
final class YuTori {
	/*********************** オブジェクトツリープロパティ ***********************/
	/**
	 * 全てprivateプロパティです。
	 * 
	 * $objInstance[オブジェクトID]
	 *   オブジェクトのインスタンスを格納
	 * $objNameShort[オブジェクトID]
	 *   オブジェクトのツリー上名称（短縮名）を格納
	 * $objNameFull[オブジェクトID]
	 *   オブジェクトのツリー上名称（フルネーム）を格納
	 * $objChildren[オブジェクトID]
	 *   オブジェクトの配下オブジェクトIDを配列として格納
	 * $objParent[オブジェクトID]
	 *   オブジェクトの親オブジェクトIDを格納
	 */
	private $objInstance;
	private $objNameShort;
	private $objNameFull;
	private $objChildren;
	private $objParent;

	/*************************** セレクタープロパティ ***************************/
	/**
	 * 全てprivateプロパティです。
	 * 
	 * $selCursor
	 *   現在選択されているオブジェクトID
	 */
	private $selCursor;
	
	/**
	 * コンストラクタの封印
	 */
	private function __construct () {
		$this->objInstance  = array(0);
		$this->objNameShort = array('YuTori');
		$this->objNameFull  = array('YuTori');
		$this->objChildren  = array(array());
		$this->objParent    = array(0);

		$this->selCursor = array();
		return;
	}
	
	/**
	 * 初期設定
	 */
	public function init () {
		// YAMLローダのインクルード
		require_once (DIR_BASE_SYS.'/lib/yamlreader.class.php');

		Y('App+' , YuTori::loadObject(DIR_BASE_SYS.'/app/index'));
		
		// 一旦デフォルトデリゲータを読み込む
		// 効率が悪いかもしれない……。
		Y('Module+' , new YuToriModule ());
		$Helper = new YuToriModule ();
		$Helper->PathExt = DIR_BASE_SYS.'/helpers/extend/hlp';
		$Helper->PathDef = DIR_BASE_SYS.'/helpers/default/hlp';
		Y('Helper+' , $Helper);

		// Systemクラス全ロード
		foreach ((array)Y('App.system') as $Key=>$SysModule) {
			if ($SysModule) {
				$Object = Y('Module.load', $SysModule);
				Y($Key .'+', $Object);
			}
		}
		
		return;
	}

	/**
	 * Singletonインスタンスの取得
	 */
	public static function &getInstance() {
		static $Instance;
		if (!isset($Instance)) {
			$Instance = new YuTori();
			$Instance->init();
		}
		return $Instance;
	}


	/**
	 * オブジェクトが存在するかどうか調べる
	 */
	public function objExists ($ID) {
		$ID = (int) $ID;
		if (array_key_exists($ID, $this->objInstance) && $this->objNameFull[$ID]) {
			return true;
		} else {
			return false;
		}
	}

	/**
	 * オブジェクトのIDから名前を取得する
	 */
	public function objNameByID ($ID, $FullName = false) {
		$ID = (int) $ID;
		if ($ID == 0) return 'YuTori';
		if ($this->objExists($ID)) {
			if ($FullName) {
				return $this->objNameFull[$ID];
			} else {
				return $this->objNameShort[$ID];
			}
		}
		return false;
	}

	/**
	 * 指定オブジェクトに子オブジェクトを追加する
	 */
	public function set ($ParentID, $Name, $Object) {
		$ParentID = (int) $ParentID;
		$Name = trim ($Name);
		$pCursor = new YuTori_selCursor ($ParentID);

		// 存在しない親オブジェクトは指定できない
		if (($ParentName = $this->objNameByID($ParentID, true)) === false) return false;
		
		// Nameに空も指定できない
		if (!$Name && !is_numeric ($Name)) {
			throw new YuToriException ('オブジェクト名が指定されていない');
		}

		$Args = array_slice (func_get_args(), 1);

		// ID=0(このオブジェクト)以外の場合、setメソッドがないか確認
		if ($ParentID != 0 && method_exists($this->objInstance[$ParentID], 'set')) {

			$pRt = $this->objInstance[$ParentID]->set($Args);
			if ($pRt === false) return false;
		}

		// 同階層のオブジェクトに同じ短縮名が指定されていないか確認
		$ID = count($this->objInstance);

		$Insert = true;
		foreach ((array)$this->objChildren[$ParentID] as $childID) {
			if (strcasecmp($this->objNameByID($childID), $Name) === 0) {
				$ID = $childID;
				$Insert = false;

				// 配下オブジェクトを全てクリアする
				foreach ((array)$this->objChildren[$ID] as $clearID) {
					$this->clear($clearID);
				}
				
				break;
			}
		}

		// オブジェクトのセット
		// 既に同じインスタンスがある場合、参照になる。
		if (is_object($Object)) {
			foreach ($this->objInstance as $iID => $Instance) {
				if ($Instance === $Object) {
					$this->objInstance[$ID]  = $Instance;
					$this->objNameShort[$ID] = $Name;
					$this->objChildren[$ID]  = &$this->objChildren[$iID];
					$this->objParent[$ID]    = $ParentID;
					if ($ParentID) {
						$this->objNameFull[$ID]  = $ParentName.'.'.$Name;
					} else {
						$this->objNameFull[$ID]  = $Name;
					}
					if ($Insert) {
						$this->objChildren[$ParentID][] = $ID;
					}
					return $ID;
				}
			}
		}
		$this->objInstance[$ID]  = YuTori::create($Object);
		$this->objNameShort[$ID] = $Name;
		$this->objChildren[$ID]  = array();
		$this->objParent[$ID]    = $ParentID;
		if ($ParentID) {
			$this->objNameFull[$ID]  = $ParentName.'.'.$Name;
		} else {
			$this->objNameFull[$ID]  = $Name;
		}

		if ($Insert) {
			$this->objChildren[$ParentID][] = $ID;
		}
		
		// オブジェクトのinitメソッドを動作させる
		if (is_object ($this->objInstance[$ID]) && method_exists($this->objInstance[$ID], 'init')) {
			$Cursor = new YuTori_selCursor ($ID);

			$Rt = $this->objInstance[$ID]->init($Args);
			if ($Rt === false) {
				$this->clear($ID);
				return false;
			} else {
				return $Rt;
			}
		} else {
			return $ID;
		}
	}

	/**
	 * 指定オブジェクトのインスタンスを返す
	 */
	public function get ($ID, &$Args = array()) {
		$ID = (int)$ID;
//		if ($this->objNameByID($ID) == false) return false;

		if ($this->objExists($ID)) {
			$Cursor = new YuTori_selCursor ($ID);

			if (is_object ($this->objInstance[$ID]) && method_exists($this->objInstance[$ID], 'get')) {
				if ($this->objInstance[$ID]->get($Args) === false) return false;
			}
			return $this->objInstance[$ID];
		} else {
			return null;
		}
	}

	/**
	 * 指定オブジェクトを削除する
	 */
	public function clear($ID) {
		$ID = (int)$ID;
		if ($ID < 1) return;

		$Cursor = new YuTori_selCursor ($ID);

		if ($this->objExists($ID)) {
			if (method_exists($this->objInstance[$ID], 'clear')) {
				if ($this->objInstance[$ID]->clear(func_get_args()) === false) return false;
			}
			if (count($this->objChildren[$ID])) {
				foreach ($this->objChildren[$ID] as $Children) {
					$this->clear($Children);
				}
			}
			$this->objInstance[$ID]  = null;
			$this->objNameShort[$ID] = null;
			$this->objNameFull[$ID]  = null;
			$this->objChildren[$ID]  = array();
			$this->objParent[$ID]    = null;
			return true;
		} else {
			return;
		}
	}

	/**
	 * 指定オブジェクトをcallする。
	 */
	public function call ($ID, &$Args = array()) {
		if ($ID == 0) return $this;
		$Cursor = new YuTori_selCursor ($ID);

		$Instance = $this->get($ID);
		if (is_object ($Instance) && method_exists($Instance, 'call')) {
			return call_user_func_array (array($Instance, 'call'), $Args);
		} else {
			return $Instance;
		}
	}
	
	/**
	 * 指定オブジェクトの配下オブジェクトを返す
	 */
	public function children ($ID) {
		$ID = (int) $ID;
		if ($this->objExists($ID)) {
			return $this->objChildren[$ID];
		}
	}

	/**
	 * 現在カーソルがあるオブジェクトIDを返す
	 */
	public function nowselCursor ($Prev = 0) {
		$Cursor = count ($this->selCursor) -1 - $Prev;
		if ($Cursor < 0) return 0;
		return $this->selCursor[$Cursor];
	}


	/**
	 * オブジェクトにカーソルをセットする
	 */
	public function setselCursor ($objID) {
		if (count ($this->selCursor) > 100) {
			$Trace = debug_backtrace();
			print ('<b>簡易ループ検知に引っかかりました。</b><br />');
			foreach ($Trace as $Key=>$ID) {
				if (!empty ($Trace[$Key]['class'])) print ('クラス:'.$Trace[$Key]['class'].'<br />');
				if (!empty ($Trace[$Key]['function'])) print ('メソッド:'.$Trace[$Key]['function'].'<br />');
				print ('<br />');
			}
			exit ();
		}
		$this->selCursor[] = (int)$objID;
		return;
	}

	/**
	 * セットしたカーソルを解除する
	 */
	public function removeselCursor () {
		$Rt = array_pop($this->selCursor);
		return $Rt;
	}

	/**
	 * オブジェクトローダー実態
	 * .区切りで子オブジェクトを選択。
	 */
	public function select($Selecter = null, $Args = array()) {
		$Selecter = trim ($Selecter);
		// 引数が空の時は自分自身を返して終了
		if (!$Selecter) return $this;

		// 引数が数値の時は対象のオブジェクトIDをcallする
		if (is_numeric ($Selecter)) {
			$Cursor = new YuTori_selCursor ($Selecter);
			$Rt = $this->call($Selecter, $Args);
			return $Rt;
		}

		// 不要な文字を排除する
		$SelStr = rtrim($Selecter,'+-<>$');
		$SelStr = strtr ($SelStr, " \t\r\n", '');

		if (empty($SelStr)) return $this;

		// thisで始まっている場合、現在のカーソルと置き換える
		if (strncasecmp($SelStr, 'this.', 5) === 0) {
			if ($pID = $this->nowselCursor()) {
				$SelStr = $this->objNameByID($pID, true). substr ($SelStr, 4);
			} else {
				$SelStr = substr ($SelStr, 5);
			}
		} elseif (strcasecmp($SelStr, 'this') === 0){
			$SelStr = $this->objNameByID($this->nowselCursor(), true);
		}

		// set時は親オブジェクトのIDを検索する
		if (($Op = substr ($Selecter, -1, 1)) == '+') {
			if (!$NewName = strrchr ($SelStr, '.')) $NewName = $SelStr;
			$SelStr = substr ($SelStr, 0, strrpos($SelStr, '.'));
			$NewName = trim ($NewName,'.');
		}

		$ID = 0;
		if ($SelStr) {
			// デリミタでセレクタを分割
			$arrSelector = explode ('.',$SelStr);
			$pID = 0;
			foreach ($arrSelector as $ShortName) {
				$tmpID = $pID;
				foreach ((array)$this->children($pID) as $Children) {
					if (strcasecmp ($this->objNameByID($Children), $ShortName) === 0) {
						$pID = $Children;
						break;
					}
				}
				// 見つからなかった場合break
				if ($tmpID == $pID) {
					$pID = 0;
					break;
				}
			}
			if ($pID) {
				$ID = $pID;
			} else {
				return null;
			}
		}

		// オペレータによって動作を変える
		// メソッドが参照返しのため、プロパティは一旦変数に代入してからreturn
		switch ($Op) {
			case ('+'):
				$Rt = $this->set($ID, $NewName, $Args[0]);
				return $Rt;
			case ('-'):
				$Rt = $this->clear($ID);
				return $Rt;
			case ('>'):
				$Rt = $this->objChildren[$ID];
				return $Rt;
			case ('<'):
				$Rt = $this->objParent[$ID];
				return $Rt;
			case ('$'):
				$Rt = $this->get($ID);
				return $Rt;
			default:
				if ($ID) {
					$Rt = $this->call($ID, $Args);
					return $Rt;
				} else {
					return null;
				}
		}
		

/* 旧セレクター
		$Selects = explode ('.', $SelStr);
		$objID = 0;
		$pID = 0;
		foreach ($Selects as $Select) {
			$Select = trim ($Select, '.');
			if (!$Select && $Select !== '0') return null;
			if (strcasecmp ($Select, 'this')===0) {
				$objID = $this->objCursor();
				continue;
			}
			
			$pID = $objID;
			if (isset($this->objTree[$objID])) {
				foreach ($this->objTree[$objID] as $cID) {
					if (isset($this->objName[$cID]) && strcasecmp($this->objName[$cID], $Select) === 0) {
						$objID = $cID;
						break;
					}
				}
			}
			if ($pID == $objID) {
				$objID = null;
				break;
			}
		}

		if (preg_match('/[\+\-\>\<\$]$/', $Selecter, $match)) {
			switch ($match[0]) {
				case ('+'):
					$this->setobjCursor($pID);
					$Rt = $this->set($pID, $Select, $Args[0]);
					$this->removeobjCursor();
					return $Rt;
				case ('-'):
					if ($objID === null) return null;
					$this->setobjCursor($objID);
					$Rt = $this->clear($objID);
					$this->removeobjCursor();
					return $Rt;
				case ('>'):
					if ($objID === null) return null;
					$this->setobjCursor($objID);
					$Rt = $this->children($objID);
					$this->removeobjCursor();
					return $Rt;
				case ('<'):
					if ($objID === null) return null;
					$this->setobjCursor($objID);
					$Rt = $this->parents($objID);
					$this->removeobjCursor();
					return $Rt;
				case ('$'):
					if ($objID === null) return null;
					$this->setobjCursor($objID);
					$Rt = $this->get($objID);
					$this->removeobjCursor();
					return $Rt;
				default:
					return null;
			}
			

		} else {
			if ($objID === null) return null;
			else {
				$this->setobjCursor($objID);
				$Rt = $this->call($objID, $Args);
				$this->removeobjCursor();
				return $Rt;
			}
		}
*/
	}


	/**
	 * カーソルが合っているオブジェクト情報を返す
	 */
	static function nowObject ($Prev = 0) {
		$Y = YuTori::getInstance ();
		$Ret = new stdClass ();
		$Ret->ID = $Y->nowselCursor ($Prev);
		$Ret->Name = $Y->objNameByID ($Ret->ID);
		$Ret->FullName = $Y->objNameByID ($Ret->ID, true);
		$Ret->Object = $Y->get ($Ret->ID);
		return $Ret;
	}

	/**
	 * オブジェクトを生成して返す。
	 * 第１引数にオブジェクトのインスタンス、第２引数にコールバック命令名を指定すると、
	 * callback型となる。
	 */
	static function &create ($Object, $Callback = null) {
		$Type = gettype ($Object);
		$Return = null;
		switch ($Type) {
			case ('object'):
				if (!is_null($Callback)) {
					$Return = new _callback ($Object, $Callback);
					break;
				} else {
					$Return = $Object;
					break;
				}
			case ('array'):
				$Return = new _array ($Object);
				break;
			default:
				$Return = $Object;
				break;
		}
		return $Return;
	}

	/**
	 * YAMLもしくは.class.phpをロードする
	 * 優先は.class.php
	 */
	static function &loadObject ($FilePath) {
		$Null = null;
		$FilePath = strtolower ($FilePath);
		if (file_exists($FilePath. '.class.php')) {
			require_once ($FilePath. '.class.php');
			$Name = basename ($FilePath);
			$Instance = new $Name ();
			return $Instance;
		} elseif (file_exists($FilePath. '.yml')) {
			$Arr = YAMLReader::Load($FilePath. '.yml');
			return $Arr;
		} else {
			return $Null;
		}
	}

}

/**
 * オブジェクトカーソルクラス
 * インスタンス生成時にカーソルをセットし、破棄時にカーソルを解除する
 */
final class YuTori_selCursor {
	public function __construct ($objID) {
		$Y = YuTori::getInstance();
		$Y->setselCursor ($objID);
	}
	
	public function __destruct () {
		$Y = YuTori::getInstance();
		$Y->removeselCursor ();
	}
}

/**
 * 例外クラスを拡張
 */
class YuToriException extends Exception {
	public function __construct ($message = "", $code = 0) {
		if (is_array ($message)) $message = implode ($message, "\n");
		return parent::__construct($message, $code);
	}
}

/**
 * デフォルトモジュールデリゲータ
 */
class YuToriModule {
	public $PathExt;
	public $PathDef;

	public $Name;
	public $ListID;

	public function __construct () {
		$this->PathExt = DIR_BASE_SYS.'/modules/extend/mod';
		$this->PathDef = DIR_BASE_SYS.'/modules/default/mod';
		
	}
	
	public function init ($Args) {
		$this->Name = $Args[0];
		Y('this.list+', array());
		Y('this.initModule+', null);

		Y('this.Path+', YuTori::Create($this, 'Path'));
		Y('this.load+', YuTori::Create($this, 'load'));
		Y('this.loadAction+', YuTori::Create($this, 'loadAction'));

		return;
	}

	/**
	 * モジュールのパスを取得する
	 */
	public function Path ($Module) {
		$Module = strtolower ($Module);

		$FilePath = $this->PathExt. $Module;
		if (!is_dir ($FilePath)) {
			$FilePath = $this->PathDef. $Module;
			if (!is_dir ($FilePath)) {
				if (Y('Logger.Debug$')) Y('Logger.Debug', 'モジュール '.$Module. ' が見つかりません。');
				return false;
			}
		}
		return $FilePath;
	}

	/**
	 * モジュールをロードする
	 * 
	 */
	public function load ($ClassName) {
		$ClassName = strtolower ($ClassName);
		if (Y($this->Name.'.List.'.$ClassName.'$')) return true;

		Y('Logger.Debug', 'モジュールロード開始');

		if (!class_exists($ClassName)) {
			if (!$Path = $this->Path($ClassName)) {
				if (Y('Logger.Debug$')) Y('Logger.Debug', '存在しないモジュール '.$ClassName. ' を読み込もうとしました。');
				return false;
			}
	
			$Path .= '/'.$ClassName.'.class.php';
	
			if (!file_exists($Path)) {
				if (Y('Logger.Debug$')) Y('Logger.Debug', '存在しないモジュール '.$ClassName. ' を読み込もうとしました。');
				return false;
			}
	
			require_once ($Path);
		}

		if (Y('Logger.Debug$')) Y('Logger.Debug', 'モジュール '.$ClassName. ' をロード');
		$Object = new $ClassName ();
		Y($this->Name.'.List.'.$ClassName.'+',$Object);
		return $Object;
	}

	/**
	 * ロードとコールを一度に行う
	 */
	public function loadAction ($Class) {
		if ($this->load($Class) == false) return false;
		$Args = func_get_args();
		$Args = array_slice($Args, 1);
		return call_user_func_array('Y', array_merge(array($this->Name.'.List.'.$Class), $Args));
	}
}

?>